/**
 * @file rf24.h
 * @brief The main header for the RF24 library
 * @author Jeff Eaton
 */

#include <xc.h>
#include <stdint.h>
#include <stdbool.h>

#include "rf_spi.h"

#ifndef RF24_H
#define	RF24_H

// instructions
#define R_REGISTER          0x00
#define W_REGISTER          0x20
#define R_RX_PAYLOAD        0x61
#define W_TX_PAYLOAD        0xA0
#define FLUSH_TX            0xE1
#define FLUSH_RX            0xE2
#define REUSE_TX_PL         0xE3
#define R_RX_PL_WID         0x60
#define W_ACK_PAYLOAD       0xA8
#define W_TX_PAYLOAD_NOACK  0xB0
#define NOP                 0xFF

// register addresses
#define CONFIG      0x00
#define EN_AA       0x01
#define EN_RXADDR   0x02
#define SETUP_AW    0x03
#define SETUP_RETR  0x04
#define RF_CH       0x05
#define RF_SETUP    0x06
#define STATUS      0x07
#define OBSERVE_TX  0x08
#define RPD         0x09
#define RX_ADDR_P0  0x0A
#define RX_ADDR_P1  0x0B
#define RX_ADDR_P2  0x0C
#define RX_ADDR_P3  0x0D
#define RX_ADDR_P4  0x0E
#define RX_ADDR_P5  0x0F
#define TX_ADDR     0x10
#define RX_PW_P0    0x11
#define RX_PW_P1    0x12
#define RX_PW_P2    0x13
#define RX_PW_P3    0x14
#define RX_PW_P4    0x15
#define RX_PW_P5    0x16
#define FIFO_STATUS 0x17
#define DYNPD	    0x1C
#define FEATURE	    0x1D

// helper stuff
typedef enum {RF24_MINUS18_DBM = 0, RF24_MINUS12_DBM, RF24_MINUS6_DBM, RF24_0_DBM} RF24_PWR_LVL;
typedef enum {RF24_250KBPS = 0, RF24_1MBPS, RF24_2MBPS} RF24_SPEED;
typedef enum {RF24_CRC_ONE_BYTE = 0, RF24_CRC_TWO_BYTES, RF24_CRC_DISABLE} RF24_CRC_MODE;
typedef enum {RF24_AW_3BYTES = 0, RF24_AW_4BYTES, RF24_AW_5BYTES} RF24_AW;
#define REG_MASK 0x1F


/**
 * @struct debug_registers
 * @brief structure for storing the values of all rf24 module registers (for debugging)
 */
typedef struct {
    uint8_t config;
    uint8_t en_aa;
    uint8_t en_rxaddr;
    uint8_t setup_aw;
    uint8_t setup_retr;
    uint8_t rf_ch;
    uint8_t rf_setup;
    uint8_t status;
    uint8_t observe_tx;
    uint8_t rpd;
    uint64_t rx_addr_p0;
    uint64_t rx_addr_p1;
    uint8_t rx_addr_p2;
    uint8_t rx_addr_p3;
    uint8_t rx_addr_p4;
    uint8_t rx_addr_p5;
    uint64_t tx_addr;
    uint8_t rx_pw_p0;
    uint8_t rx_pw_p1;
    uint8_t rx_pw_p2;
    uint8_t rx_pw_p3;
    uint8_t rx_pw_p4;
    uint8_t rx_pw_p5;
    uint8_t fifo_status;
    uint8_t dynpd;
    uint8_t feature;
    
} debug_registers;


// function prototypes

/**
 * Initialize the chip and get everything ready to use.
 * Call this before calling anything else
 *
 * @warning This assumes that the TRIS registers for the CE and IRQ pins are
 * properly set!  Make sure you have the CE pin set as an output (0) and the IRQ
 * pin set as an input (1).  Also make sure the analog capability on both pins
 * is disabled. (AD1PCFGL, bit set = disabled)
 *
 * @warning This function also assumes that SPI1 has been configured and enabled
 * already.  If you don't configure and enable SPI1, this library won't work at all.
 *
 * @param m_LAT_REG_CE pointer to the LAT register containing the desired CE_pin
 * @param m_CE_pin which bit in m_LAT_REG_CE is the desired CE pin
 * @param m_PORT_REG_IRQ pointer to the PORT register containing the desired IRQ pin
 * @param m_IRQ_pin which bit in m_PORT_REG_IRQ is the desired IRQ pin
 */
void rf_init(volatile uint16_t* m_LAT_REG_CE, const uint8_t m_CE_pin,
        volatile uint16_t* m_PORT_REG_IRQ, const uint8_t m_IRQ_pin);


/**
 * Function to flush the contents of the TX register.
 *
 * @return STATUS register contents
 */
uint8_t rf_flushTX();


/**
 * Function to flush the contents of the RX register.
 *
 * @return STATUS register contents
 */
uint8_t rf_flushRX();


/**
 * Open a pipe for writing.  @p pipeAddr should match the read pipe address
 * of the receiving module.  The length of the address should correspond with
 * the value stored in the SETUP_AW register.
 * @see setAddressWidth
 *
 * @param pipeAddr The address of the pipe to open
 */
void rf_openWritePipe(const uint64_t pipeAddr);


/**
 * Open a pipe for reading.  @p pipeAddr should match the write pipe address
 * of the transmitting module.  Up to six pipes can be open at a time,
 * corresponding to @p num values of 0 - 5.
 *
 * @warning Pipes 1-5 must have the same address EXCEPT for the LSB.  The LSB
 * on all pipes must be different.
 *
 * @param num Which pipe to open. (0 through 5)
 * @param pipeAddr The address of the read pipe.
 */
void rf_openReadPipe(const uint8_t num, const uint64_t pipeAddr);


/**
 * Put the RF24 device into RX mode and wait for data to arrive.  Before
 * calling this function, open some pipes to listen on by using rf_openReadPipe().
 * Call rf_available() to see if there's any data to read, and then read() to
 * read it
 *
 * @see rf_openReadPipe
 *
 * @warning Don't call write() while listening - call stopListening() first.
 */
void rf_startListening();


/**
 * Call this when you want to drop out of listening mode.  Always call this
 * before you write() if you were previously reading.
 */
void rf_stopListening();


/**
 * Check and see if there is any data available to read.
 *
 * @param pipeNum Address of a variable in which to store the pipe number of
 * available data.  If data is available, the variable at this address will
 * contain the number of the pipe containing data.  If no data is available,
 * 0x07 will be written.
 *
 * @return True if there is data to read, false otherwise.
 */
bool rf_available(uint8_t* pipeNum);


/**
 * Read data from the RF module into RAM.  Call this if rf_available returns
 * true.  If you have dynamic payloads enabled, @p len should be the
 * value returned from rf_readRXPayloadWidth().
 * 
 * @warning I'm not sure what would happen if you called this while nothing
 * is available or you haven't yet called startListening().
 *
 * @see rf_available
 * @see rf_readRXPayloadWidth
 *
 * @param buf Pointer to RAM in which to store the received data
 * @param len Number of bytes to read
 * @return True if you have read all available data, false otherwise
 */
bool rf_read(uint8_t* buf, uint8_t len);


/**
 * Write data from RAM into the RF module and transmit it.  Make sure you
 * have an open writing pipe before you call this.
 *
 * @see rf_openWritePipe
 *
 * @warning Don't call this if you are actively listening for data!  Call
 * rf_stopListening() first.
 *
 * @param buf Data to write
 * @param len Number of bytes to write
 */
void rf_write(const uint8_t* buf, uint8_t len);


/**
 * Reuse last transmitted payload.  This will be active until either rf_flushTX()
 * or rf_writePayload() is called.
 *
 * @return STATUS register contents
 */
uint8_t rf_reuseTXPayload();


/**
 * Enable the "dynamic payloads" feature of the RF module.  This allows
 * packet payloads to be any size between 0 and 32 bytes, instead of forcing
 * all packet payloads to be the same size.
 *
 * @param enable True to enable dynamic payloads, false to disable
 */
void rf_enableDynamicPayloads(const bool enable);


/**
 * Enable the "ACK Payload" feature of the RF module.  This allows a receiving
 * module to automatically send back data attached to an ACK packet.  Enabling
 * this feature also enables dynamic payloads, which are required for ACK
 * payloads to function.
 *
 * @see rf_enableDynamicPayloads
 *
 * @param enable True to enable ACK payloads, false to disable
 */
void rf_enableACKPayloads(const bool enable);


/**
 * --- NYI ---
 * Look at the status register and figure out what fired the interrupt.
 * This would make it possible to do non-blocking writes and handle the
 * ACK with an interrupt
 * 
 * @return STATUS register contents
 */
uint8_t rf_handleIRQ();


/**
 * Read the width of the first payload in the RX buffer.  This is used when
 * dynamic payload width is enabled.  If this value is greater than 32, the
 * RX buffer will be flushed automatically.
 *
 * @return Number of bytes in the top RX payload
 */
uint8_t rf_readRXPayloadWidth();


/**
 * Set the width of expected payloads on pipe @p num.  This allows the read
 * function to return the correct amount of data when dynamic payloads is
 * disabled.
 *
 * Passing a @p num value greater than 5 will set the width for all RX pipes.
 *
 * @param num Which read pipe the width is being set for
 * @param width Number of bytes in incoming payloads
 */
void rf_setStaticPayloadWidth(const uint8_t num, const uint8_t width);


/**
 * Write an ACK Payload to a given RX Pipe.  When a packet is received on
 * that pipe, the ACK Payload will be attached to the ACK packet and sent
 * back to the other module.
 *
 * @see rf_enableACKPayloads
 *
 * @param pipe Which RX pipe the ACK payload will be attached to (0-5)
 * @param buf Data to be sent in the ACK payload
 * @param len Number of bytes to send
 * @return STATUS register contents
 */
uint8_t rf_writeACKPayload(const uint8_t pipe, const uint8_t* buf, uint8_t len);


/**
 * Reads the status register.
 *
 * @return STATUS register contents
 */
uint8_t rf_readStatus();


/**
 * Set the power amplifier level.
 *
 * @param pwr one of the power levels defined in the RF24_PWR_LVL enum in rf24.h
 * @return STATUS register contents
 */
void rf_setPALevel(const RF24_PWR_LVL pwr);


/**
 * Set the rate at which data is transmitted.
 *
 * @param speed One of the speed values defined in the RF24_SPEED enum in rf24.h
 * @return STATUS register contents
 */
void rf_setDataRate(const RF24_SPEED speed);


/**
 * Set the communications channel.  This ranges from 2.4GHz to ~2.525GHz.  Valid
 * input values are between 0 and 127.
 *
 * @param channel Which frequency channel to operate on
 * @return STATUS register contents
 */
void rf_setChannel(const uint8_t channel);


/**
 * Set the CRC mode of communications.  Possible options are one byte and two
 * byte CRC.
 *
 * @param mode One of the RF24_CRC_MODE values defined in the enum in rf24.h
 * @return STATUS register contents
 */
void rf_setCRCMode(const RF24_CRC_MODE mode);


/**
 * Set the "No ACK" mode for all transmitted packets.  Enabling this means
 * that the transmitter will not wait and listen for an ACK packet after
 * each transmission.
 *
 * @param noack True to enable "No ACK" mode, false to disable  (true = no acks, false = acks)
 */
void rf_setNoACK(const bool noack);


/**
 * Set the time between transmission retries as well as the number of retries
 * attempted before a packet fails to send.
 *
 * @param delay Time between retry attempts (0-15)
 * @param count Maximum number of retries (0-15)
 */
void rf_setRetries(const uint8_t delay, const uint8_t count);


/**
 * Set the address width to be used with read and write pipes.  Valid values
 * are found in the RF24_AW enum.
 *
 * @see openReadPipe
 * @see openWritePipe
 *
 * @param width Address width to be used
 */
void rf_setAddressWidth(const RF24_AW width);


/**
 * Test the received power.  This is a useful function to check for a carrier
 * frequency or interference on a given channel.
 *
 * @return True if received power is above -64dBm, false otherwise
 */
bool rf_testRPD();


/**
 * Transmit a constant carrier wave on the current channel.  This function is
 * used for testing various things including range, interference, if your
 * stuff is wired correctly, etc.
 *
 * @warning Only use for testing!  Don't call this during normal tx/rx
 * operation
 *
 * @param enable True to enable carrier signal, false to disable
 */
void rf_sendCarrier(const bool enable);


/**
 * Read all device registers and store them in the debug_registers struct @p reg.
 * This is only used when testing and debugging, and should definitely not be
 * used during high speed communications as it takes quite a bit of time to
 * read every register.
 *
 * @param reg pointer to struct in which to store register values
 */
void rf_debug(debug_registers* reg);

#endif	/* RF24_H */
